package com.boat.starter.xxljob.filter;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.stereotype.Component;

import com.boat.framework.common.thread.context.AppContext;
import com.boat.framework.common.thread.context.ContextUtils;
import com.boat.framework.common.thread.context.LogContext;
import com.boat.framework.common.thread.context.MockContext;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.context.XxlJobHelper;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

/**
 * 解决异常捕获，打印跟踪号，统一打印日志
 * @author 周江
 *
 */
@Slf4j
@Aspect
@Component
@ConditionalOnClass(name="com.xxl.job.core.handler.annotation.XxlJob")
public class XxljobFilter {
	@Pointcut(" @annotation(com.xxl.job.core.handler.annotation.XxlJob)")
	public void proxyAspect() {
	}

	@Around("proxyAspect()")
	public Object doInvoke(ProceedingJoinPoint joinPoint) {
		String className = joinPoint.getTarget().getClass().getSimpleName();
		String methodName = joinPoint.getSignature().getName();
		
		TaskLog taskLog = null;
		try {
			// 生成跟踪号
			LogContext.newLogContext(null);
			
			taskLog = TaskLog.createTaskLog(className, methodName);
			//定时任务开始
			taskStart(taskLog);

			Object result = joinPoint.proceed();
			//定时任务成功
			taskSuccess(taskLog);
			
			return result;
		} catch (Throwable ex) {
			log.warn("任务执行失败，错误原因：" + ex.getMessage(), ex);
			//定时任务失败
			taskFail(taskLog, ex);
			return ReturnT.FAIL;
		} finally {
			// 清除跟踪号
			ContextUtils.clear();
		}
	}
	
	private void taskStart(TaskLog taskLog) {
		if (taskLog.isXxljob()) {
			String param = XxlJobHelper.getJobParam();
			log.info("定时任务执行开始。task:{}, 入参：{}", taskLog.getName(), param);
			XxlJobHelper.log("定时任务执行开始。task:{}, 入参：{}", taskLog.getName(), param);
		}
	}
	
	private void taskSuccess(TaskLog taskLog) {
		taskLog.success();
		String loginfo = taskLog.toString();
		log.info(loginfo);
		if (taskLog.isXxljob()) {
			XxlJobHelper.handleSuccess("任务执行成功");
		}
	}

	private void taskFail(TaskLog taskLog, Throwable ex) {
		taskLog.fail(ex);
		String loginfo = taskLog.toString();
		log.info(loginfo);
		if (taskLog.isXxljob()) {
			XxlJobHelper.handleFail("任务执行失败：" + StrUtil.maxLength(taskLog.getRemark(), 20));
		}
	}
	
	@Data
	public static class TaskLog {
		private static String TASKTYPE_SCHEDULED="scheduled";
		private static String TASKTYPE_XXLJOB="xxljob";
		
		private String name;
		private boolean result;
		private Date startTime;
		private Date endTime;
		private String times;// 单位秒，如：150
		private String timesPretty;// 格式化的耗时时间，如：2分30秒
		private String remark;
		private String taskType = TASKTYPE_SCHEDULED; //任务类型：scheduled,xxljob

		// -----------工厂方法------------------
		public static TaskLog createTaskLog(String className, String methodName) {
			String name = className + "." + methodName;
			TaskLog taskLog = new TaskLog();
			taskLog.setName(name);
			taskLog.setStartTime(new Date());
			taskLog.setTaskType(TASKTYPE_XXLJOB);
			return taskLog;
		}

		// -----------工厂方法------------------
		public void success() {
			this.setResult(true);
			this.setEndTime(new Date());
			this.setRemark("");
			this.betweenTimes();
			this.betweenTimesPretty();
		}

		public void fail(Throwable ex) {
			this.setResult(false);
			this.setEndTime(new Date());
			this.setRemark(ex.getMessage());
			this.betweenTimes();
			this.betweenTimesPretty();
		}
		
		public boolean isXxljob() {
			return TASKTYPE_XXLJOB.equals(this.getTaskType());
		}

		private void betweenTimes() {
			long betweenMs = this.endTime.getTime() - this.startTime.getTime();
			double betweenS = Double.valueOf(betweenMs) / 1000;
			// 精确到毫秒
			BigDecimal bg = BigDecimal.valueOf(betweenS).setScale(3, RoundingMode.DOWN);
			// 去除结尾是0或.
			this.times = NumberUtil.toStr(bg.doubleValue());
		}

		private void betweenTimesPretty() {
			// 返回格式：1天2小时35分5秒
			long intervalMs = this.endTime.getTime() - this.startTime.getTime();
			this.timesPretty = DateUtil.formatBetween(intervalMs);
		}

		private static String MONITOR_LOG_TMPL = "定时任务执行完毕。task:{},result:{},times:{},timesPretty:{}";

		@Override
		public String toString() {
			return StrUtil.format(MONITOR_LOG_TMPL, this.getName(),this.isResult(), this.getTimes(),
					this.getTimesPretty());
		}
	}
}
